<?php
/**
 * SQL语句执行类
 * $Id$
 */

class sql_exe {
	/**
	 * 记录最后的错误信息
	 */
	private $error = '';

	/**
	 * 数据库对象
	 */
	private $db = '';

	/**
	 * 记录当前执行的SQL文件
	 */
	private $current_file = '';

	/**
	 * 替换前表前缀
	 */
	private $source_prefix = '';

	/**
	 * 替换后表前缀
	 */
	private $target_prefix = '';

	/**
	 * 当发生错误时，程序把日志记录在指定文件中
	 */
	private $log_patch = '';

	/**
	 * 智能化查询
	 */
	private $auto_match = false;
	
	/**
	 * 构造函数 初始化成员变量
	 */
	public function __construct($db, $auto_match = false) {
		$this->db = $db;
		$this->auto_match = $auto_match;
	}

	/**
	 * 执行SQL文件中的所有语句
	 */
	public run_all_file($sql_files) {
		foreach ($sql_files as $sql_file) {
			$query_items = $this->perse_sql_file($sql_file);
			if (!$query_items) continue;	/* 如果解析失败 跳过*/
			
			foreach ($query_items as $query_item) {
				if (!$query_item) {
					continue;
				}
				if (!$this->query($query_item)) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 从SQL文件中提取每个SQL语句
	 */
	public function parse_sql_file($file_path) {
		if (!file_exists($file_path) {
			return false;
		}
		/* 当前执行的SQL文件*/
		$this->current_file = $file_path;

		/* 读取文件*/
		$all_sql = implode('',file($file_path));

		/* 删除文件注释*/
		$all_sql = $this->remove_comment($all_sql);

		/* 删除首尾空白符*/
		$all_sql = trim($all_sql);

		/* 如果没有语句*/
		if (!$all_sql) {
			return false;
		}

		/* 替换表前缀*/
		$all_sql = $this->replace_prefix($all_sql);
		/* 解析查询语句*/
		$all_sql = str_replace("\r",'',$all_sql);
		$query_items = explode(";\n",$all_sql);

		return $query_items;
	}

	/**
	 * 执行单个查询语句
	 */
	public function query($query_item) {
		/* 删除首尾空白符*/
		$query_item = trim($query_item);
		
		/* 如果查询选项为空 返回false*/
		if (!$sql) {
			return false;
		}
		
		/* 处理表操作*/
		if (preg_match('/^\s*CREATE\s+TABLE\s*/i', $query_item) {
			if (!$this->create_table($query_item)) {
				return false;
			}
		} elseif ($this->auto_match && preg_match('/^\s*ALTER\s+TABLE\s*/i', $query_item)) {
			/* 处理ALTER TABLE语句，此时将对表的结构进行修改*/
			if (!$this->alter_table($query_item)) {
				return false;
			}
		} else {
			/* 处理其他操作，如数据添加 更新 删除等*/
			if (!$this->do_other($query_item)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 删除SQL查询串中的注释
	 */
	public function remove_comment($sql) {
		/* 删除行注释，行注释不匹配还行符*/
		$sql = preg_replace('/^\s*(?:--|#).*/m', '', $sql);

		/* 删除注释块，匹配还行符(修饰符s让.可以代表还行符)*/
		$sql = preg_replace('/^\s*\/\*.*?\*\//ms', '', $sql);

		return $sql;
	}

	/**
	 * 从查询语句中获得表名称
	 */
	public function get_table_name($query_item, $query_type = '') {
		$pattern = '';
		$matches = array();
		$table_name = '';

		/* 如果没有指定$query_type，则自动获取*/
		if (!$query_type && $preg_match('/^\s*(\w+)/', $query_item, $matches)) {
			$query_type = $matches[1];
		}

		/* 获得相应的正则表达式*/
		$query_type = strtoupper($query_type);
		switch ($query_type) {
			case 'ALTER':
				$pattern = '/^\s*ALTER\s+TABLE\s*`?(\w+)/i';
				break;
			case 'CREATE':
				$pattern = '/^\s*CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s*`?(\w+)/i';
				break;
			case 'DROP':
				$pattern = '/^\s*DROP\s+TABLE(?:\s+IF\s+EXISTS)?\s*`?(\w+)/i';
				break;
			case 'INSERT':
				$pattern = '/^\s*INSERT\s+INTO\s*`?(\w+)/i';
				break;
			case 'REPLACE':
				$pattern = '/^\s*REPLACE\s+INTO\s*`?(\w+)/i';
				break;
			case 'UPDATE':
				$pattern = '/^\s*UPDATE\s*`?(\w+)/i';
				break;
			default :
				return false;
		}
		if (!preg_match($pattern, $query_item, $matches) {
			return false;
		}
		$table_name = $matches[1];
		return $table_name;
	}

	/**
	 * 执行创建数据库SQL语句
	 */
	public function create_table($query_item) {
		/* 获取创建表的主体串以及表属性声明串，不区分大小写，且为贪婪匹配(s)*/
		$pattern = '/^\s*(CREATE\s+TABLE[^(]+\(.*\))(.*)$/is';
		if (!preg_match($pattern, $query_item, $matches)) {
			return false;
		}
		$main = $matches[1];
		$postfix = $matches[2];

		/* 从表属性中查找表的类型*/
		$pattern = '/.*(?:ENGINE|TYPE)\s*=\s*([a-z]+).*$/is';
		$type = preg_match($pattern, $postfix, $matches) ? $matches[1] : 'MYISAM';

		/* 从表属性串中查找自增语句*/
		$pattern = '/.*(AUTO_INCREMENT\s*=\s*\d+).*$/is';
		$auto_incr = preg_match($pattern, $postfix, $matches);

		/* 重新设置表属性串*/
		$postfix = $this->db->version() > '4.1' ? " ENGINE=$type DEFAULT CHARACTER SET utf8" : " TYPE=$type";
		$postfix .= ' ' . $auto_incr;

		/* 重新构建表语句*/
		$sql = $main . $postfix;

		/* 创建表*/
		if (!$this->db->query($sql)) {
			$this->handle_error($sql);
			return false;
		}
		return true;
	}

	/**
	 * 修改数据表
	 */
	public function alter_table($query_item) {
		/* 获取表名*/
		$table_name = $this->get_table_name($query_item);
		if (!$table_name) {
			return false;
		}

		/* 先把CHANGE操作提取出来执行，再过滤掉它们*/
		$result = $this->parse_change_query($query_item, $table_name);
		if ($result[0] && !$this->db->query($result[0])) {
			$this->handle_error($result[0]);
			return false;
		}
		if (!$result[1]) {
			return true;
		}

		/* 把DROP操作提取执行，再过滤掉它们*/
		$result = $this->parse_drop_column_query($result[1], $table_name);
		if ($result[0] && !$this->db->query($result[0])) {
			$this->handle_error($result[0]);
			return false;
		}
		if (!$result[1]) {
			return true;
		}

		/* 把ADD [COLUMN]操作提取出来执行，再过滤掉它们*/
		$result = $this->parse_add_column_query($result[1], $table_name);
		if ($result[0] && !$this->db->query($result[0])) {
			$this->handle_error($result[0]);
			return false;
		}
		if (!result[1]) {
			return true;
		}

		/* 把DROP INDEX提取出来执行，再过滤掉它们*/
		$result = $this->parse_drop_index_query($result[1], $table_name);
		if ($result[0] && !$this->db->query($result[0])) {
			$this->handle_error($result[0]);
			return false;
		}
		if (!$result[1]) {
			return ture;
		}

		/* 把 ADD INDEX 提取出来执行，再过滤掉它们*/
		$result = $this->parse_add_index_query($result[1]);
		if ($result[0] && !$this->db->query($result[0])) {
			$this->handle_error[$result[0]);
			return false;
		}
		/* 执行其它修改操作*/
		if ($result[1] && !$this->db->query($result[1])) {
			$this->handle_error($result[1]);
			return false;
		}
		return true;
	}

	/**
	 * 解析出CHANGE操作
	 *
	 * @access  public
	 * @param   string      $query_item     SQL查询项
	 * @param   string      $table_name     表名
	 * @return  array       返回一个以CHANGE操作串和其它操作串组成的数组
	 */
	public function parse_change_query($query_item, $table_name = '') {
		$result = array('', $query_item);
		if (!$table_name) {
			$table_name = $this->get_table_name($query_item, 'ALTER');
		}

		$matches = array();
		/* 第1个子模式匹配old_col_name，第2个子模式匹配column_definition，第3个子模式匹配new_col_name */
		$pattern = '/\s*CHANGE\s*`?(\w+)`?\s*`?(\w+)`?([^,(]+\([^,]+?(?:,[^,)]+)*\)[^,]+|[^,;]+)\s*,?/i';
		if (preg_match_all($pattern, $query_item, $matches, PREG_SET_ORDER)) {
			$fields = $this->get_fields($table_name);
			$num = count($matches);
			$sql = '';
			for ($i = 0; $i < $num; $i++) {
				/* 如果表中存在原列名 */
				if (in_array($matches[$i][1], $fields)) {
					$sql .= $matches[$i][0];
				} elseif (in_array($matches[$i][2], $fields)) {
					/* 如果表中存在新列名 */
					$sql .= 'CHANGE ' . $matches[$i][2] . ' ' . $matches[$i][2] . ' ' . $matches[$i][3] . ',';
				} else {
					/* 如果两个列名都不存在 */
					$sql .= 'ADD ' . $matches[$i][2] . ' ' . $matches[$i][3] . ',';
					$sql = preg_replace('/(\s+AUTO_INCREMENT)/i', '\1 PRIMARY KEY', $sql);
				}
			}
			$sql = 'ALTER TABLE ' . $table_name . ' ' . $sql;
			$result[0] = preg_replace('/\s*,\s*$/', '', $sql);//存储CHANGE操作，已过滤末尾的逗号
			$result[0] = $this->insert_charset($result[0]);//加入字符集设置
			$result[1] = preg_replace($pattern, '', $query_item);//存储其它操作
			$result[1] = $this->has_other_query($result[1]) ? $result[1]: '';
		}
		return $result;
	}

	/**
	 * 解析出DROP COLUMN操作
	 *
	 * @access  public
	 * @param   string      $query_item     SQL查询项
	 * @param   string      $table_name     表名
	 * @return  array       返回一个以DROP COLUMN操作和其它操作组成的数组
	 */
	public function parse_drop_column_query($query_item, $table_name = '') {
		$result = array('', $query_item);

		if (!$table_name) {
			$table_name = $this->get_table_name($query_item, 'ALTER');
		}

		$matches = array();
		/* 子模式存储列名 */
		$pattern = '/\s*DROP(?:\s+COLUMN)?(?!\s+(?:INDEX|PRIMARY))\s*`?(\w+)`?\s*,?/i';
		if (preg_match_all($pattern, $query_item, $matches, PREG_SET_ORDER)) {
			$fields = $this->get_fields($table_name);
			$num = count($matches);
			$sql = '';
			for ($i = 0; $i < $num; $i++) {
				if (in_array($matches[$i][1], $fields)) {
					$sql .= 'DROP ' . $matches[$i][1] . ',';
				}
			}
			if ($sql) {
				$sql = 'ALTER TABLE ' . $table_name . ' ' . $sql;
				$result[0] = preg_replace('/\s*,\s*$/', '', $sql);//过滤末尾的逗号
			}
			$result[1] = preg_replace($pattern, '', $query_item);//过滤DROP COLUMN操作
			$result[1] = $this->has_other_query($result[1]) ? $result[1] : '';
		}

		return $result;
	}

	/**
	 * 解析出ADD [COLUMN]操作
	 *
	 * @access  public
	 * @param   string      $query_item     SQL查询项
	 * @param   string      $table_name     表名
	 * @return  array       返回一个以ADD [COLUMN]操作和其它操作组成的数组
	 */
	public function parse_add_column_query($query_item, $table_name = '') {
		$result = array('', $query_item);

		if (!$table_name) {
			$table_name = $this->get_table_name($query_item, 'ALTER');
		}

		$matches = array();
		/* 第1个子模式存储列定义，第2个子模式存储列名 */
		$pattern = '/\s*ADD(?:\s+COLUMN)?(?!\s+(?:INDEX|UNIQUE|PRIMARY))\s*(`?(\w+)`?(?:[^,(]+\([^,]+?(?:,[^,)]+)*\)[^,]+|[^,;]+))\s*,?/i';
		if (preg_match_all($pattern, $query_item, $matches, PREG_SET_ORDER)) {
			$fields = $this->get_fields($table_name);
			$mysql_ver = $this->db->version();
			$num = count($matches);
			$sql = '';
			for ($i = 0; $i < $num; $i++) {
				if (in_array($matches[$i][2], $fields)) {
					/* 如果为低版本MYSQL，则把非法关键字过滤掉 */
					if  ($mysql_ver < '4.0.1' ) {
						$matches[$i][1] = preg_replace('/\s*(?:AFTER|FIRST)\s*.*$/i', '', $matches[$i][1]);
					}
					$sql .= 'CHANGE ' . $matches[$i][2] . ' ' . $matches[$i][1] . ',';
				} else {
					$sql .= 'ADD ' . $matches[$i][1] . ',';
				}
			}
			$sql = 'ALTER TABLE ' . $table_name . ' ' . $sql;
			$result[0] = preg_replace('/\s*,\s*$/', '', $sql);//过滤末尾的逗号
			$result[0] = $this->insert_charset($result[0]);//加入字符集设置
			$result[1] = preg_replace($pattern, '', $query_item);//过滤ADD COLUMN操作
			$result[1] = $this->has_other_query($result[1]) ? $result[1] : '';
		}

		return $result;
	}

	/**
	 * 解析出DROP INDEX操作
	 *
	 * @access  public
	 * @param   string      $query_item     SQL查询项
	 * @param   string      $table_name     表名
	 * @return  array       返回一个以DROP INDEX操作和其它操作组成的数组
	 */
	public function parse_drop_index_query($query_item, $table_name = '') {
		$result = array('', $query_item);

		if (!$table_name) {
			$table_name = $this->get_table_name($query_item, 'ALTER');
		}

		/* 子模式存储键名 */
		$pattern = '/\s*DROP\s+(?:PRIMARY\s+KEY|INDEX\s*`?(\w+)`?)\s*,?/i';
		if (preg_match_all($pattern, $query_item, $matches, PREG_SET_ORDER)) {
			$indexes = $this->get_indexes($table_name);
			$num = count($matches);
			$sql = '';
			for ($i = 0; $i < $num; $i++) {
				/* 如果子模式为空，删除主键 */
				if (empty($matches[$i][1])) {
					$sql .= 'DROP PRIMARY KEY,';
				} elseif (in_array($matches[$i][1], $indexes)) {
					/* 否则删除索引 */
					$sql .= 'DROP INDEX ' . $matches[$i][1] . ',';
				}
			}
			if ($sql) {
				$sql = 'ALTER TABLE ' . $table_name . ' ' . $sql;
				$result[0] = preg_replace('/\s*,\s*$/', '', $sql);//存储DROP INDEX操作，已过滤末尾的逗号
			}
			$result[1] = preg_replace($pattern, '', $query_item);//存储其它操作
			$result[1] = $this->has_other_query($result[1]) ? $result[1] : '';
		}
		return $result;
	}

	/**
	 * 解析出ADD INDEX操作
	 *
	 * @access  public
	 * @param   string      $query_item     SQL查询项
	 * @param   string      $table_name     表名
	 * @return  array       返回一个以ADD INDEX操作和其它操作组成的数组
	 */
	public function parse_add_index_query($query_item, $table_name = '') {
		$result = array('', $query_item);

		if (!$table_name) {
			$table_name = $this->get_table_name($query_item, 'ALTER');
		}

		/* 第1个子模式存储索引定义，第2个子模式存储"PRIMARY KEY"，第3个子模式存储键名，第4个子模式存储列名 */
		$pattern = '/\s*ADD\s+((?:INDEX|UNIQUE|(PRIMARY\s+KEY))\s*(?:`?(\w+)`?)?\s*\(\s*`?(\w+)`?\s*(?:,[^,)]+)*\))\s*,?/i';
		if (preg_match_all($pattern, $query_item, $matches, PREG_SET_ORDER)) {
			$indexes = $this->get_indexes($table_name);
			$num = count($matches);
			$sql = '';
			for ($i = 0; $i < $num; $i++) {
				$index = !empty($matches[$i][3]) ? $matches[$i][3] : $matches[$i][4];
				if (!empty($matches[$i][2]) && in_array('PRIMARY', $indexes)) {
					$sql .= 'DROP PRIMARY KEY,';
				} elseif (in_array($index, $indexes)) {
					$sql .= 'DROP INDEX ' . $index . ',';
				}
				$sql .= 'ADD ' . $matches[$i][1] . ',';
			}
			$sql = 'ALTER TABLE ' . $table_name . ' ' . $sql;
			$result[0] = preg_replace('/\s*,\s*$/', '', $sql);//存储ADD INDEX操作，已过滤末尾的逗号
			$result[1] = preg_replace($pattern, '', $query_item);//存储其它的操作
			$result[1] = $this->has_other_query($result[1]) ? $result[1] : '';
		}

		return $result;
	}

	/**
	 * 获取某个表的所有INDEX
	 */
	public function get_indexes($table_name) {
		$indexes = array();
		$result = $this->db->query('SHOW INDEX FROM '.$table_name);
		if ($result) {
			while ($row = $this->db->fetch_array($result)) {
				$indexes[] = $row['Key_name'];
			}
		}
		return $indexes;
	}

	/**
	 * 获取所有的fields
	 *
	 * @access  public
	 * @param   string      $table_name      数据表名
	 * @return  array
	 */
	public function get_fields($table_name) {
		$fields = array();

		$result = $this->db->query('SHOW FIELDS FROM '.$table_name);

		if ($result) {
			while ($row = $this->db->fetch_array($result)) {
				$fields[] = $row['Field'];
			}
		}
		return $fields;
	}

	/**
	 * 判断是否还有其它的查询
	 *
	 * @access  private
	 * @param   string      $sql_string     SQL查询串
	 * @return  boolean     有返回true，否则返回false
	 */
	public function has_other_query($sql_string) {
		return preg_match('/^\s*ALTER\s+TABLE\s*`\w+`\s*\w+/i', $sql_string);
	}

	/**
	 * 在查询串中加入字符集设置
	 *
	 * @access  private
	 * @param  string      $sql_string     SQL查询串
	 * @return  string     含有字符集设置的SQL查询串
	 */
	public function insert_charset($sql_string) {
		if ($this->db->version() > '4.1') {
			$sql_string = preg_replace('/(TEXT|CHAR\(.*?\)|VARCHAR\(.*?\))\s+/i',
					'\1 CHARACTER SET ' . $this->db_charset . ' ',
					$sql_string);
		}
		return $sql_string;
	}

	/**
	 * 处理其它的数据库操作
	 *
	 * @access  private
	 * @param   string      $query_item     SQL查询项
	 * @return  boolean     成功返回true，失败返回false。
	 */
	public function do_other($query_item) {
		if (!$this->db->query($query_item)) {
			$this->handle_error($query_item);
			return false;
		}
		return true;
	}

	/**
	 * 处理错误信息
	 *
	 * @access  private
	 * @param   string      $query_item     SQL查询项
	 * @return  boolean     成功返回true，失败返回false。
	 */
	public function handle_error($query_item) {
		$mysql_error = 'ERROR NO: ' . $this->db->errno()
					. "\r\nERROR MSG: " . $this->db->error();

		$error_str = "SQL Error:\r\n " . $mysql_error
				. "\r\n\r\n"
				. "Query String:\r\n ". $query_item
				. "\r\n\r\n"
				. "File Path:\r\n ". $this->current_file
				. "\r\n\r\n\r\n\r\n";

		if ($this->log_path) {
			$f = @fopen($this->log_path, 'ab+');
			if (!$f) {
				return false;
			}
			if (!@fwrite($f, $error_str)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 替换表前缀
	 */
	public function replace_prefix($sql) {
		$keywords = 'CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?|'
				  . 'DROP\s+TABLE(?:\s+IF\s+EXISTS)?|'
				  . 'ALTER\s+TABLE|'
				  . 'UPDATE|'
				  . 'REPLACE\s+INTO|'
				  . 'DELETE\s+FROM|'
				  . 'INSERT\s+INTO';
		$pattern = '/(' . $keywords . ')(\s*)`?' . $this->source_prefix . '(\w+)`?(\s*)/i';
		$replacement = '\1\2`' . $this->target_prefix . '\3`\4';
		$sql = preg_replace($pattern, $replacement, $sql);

		$pattern = '/(UPDATE.*?WHERE)(\s*)`?' . $this->source_prefix . '(\w+)`?(\s*\.)/i';
		$replacement = '\1\2`' . $this->target_prefix . '\3`\4';
		$sql = preg_replace($pattern, $replacement, $sql);

		return $sql;
	}
}
